from Bases import setID
from Bases import OSCBaseObject
from ..Attributes import Attribute
from ..Groups.AttributeGroup import DimmerGroup, ColorGroup, PositionGroup, ParameterGroup

propnames = ['name', 'manufacturer', 'description', 'personality_mode']

class Device(OSCBaseObject):
    '''Base for all devices.
    :Parameters:
        'name' : default is empty
        'id' : Universal ID.  Default is None (which assigns a new uuid)
        'description' : default is empty
        'manufacturer' : default is empty
        'channel_count' : int, maximum number of channels for the device.
            default is 0
        'class_name' : class specification (in progress)
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the device.  default is None.
    '''
    
    _saved_class_name = 'Device'
    _saved_attributes = ['base_class', 'id']
    #_saved_attributes = ['name', 'base_class', 'id', 'description', 'manufacturer', 'personality_mode']
    _saved_child_objects = ['Attributes']
    _Properties = dict(zip(propnames, [{'type':str, 'default':''}]*len(propnames)))
    _SettingsProperties = propnames
    _ChildGroups = {'Attributes':{}}
    def __init__(self, **kwargs):
        self._name = None
        self._manufacturer = None
        #self.attr_by_chan = {}
        super(Device, self).__init__(**kwargs)
        self.register_signal('new_attribute', 'del_attribute', 'child_added')
        if 'deserialize' not in kwargs:
            #self.Attributes = {}
            self.name           = kwargs.get('name', '')
            self.manufacturer   = kwargs.get('manufacturer', '')
            self.description    = kwargs.get('description', '')
            self.personality_mode = kwargs.get('personality_mode', '')
            self.id = setID(kwargs.get('id'))
            if hasattr(self, 'base_class') is False:
                self.base_class = kwargs.get('base_class')
            categories = kwargs.get('categories',{})
            for category in categories.itervalues():
                self.add_category(category)
#        self.register_attr_watch('name')
#        self.register_attr_watch('manufacturer')
#        self.register_attr_watch('description')
#        self.register_attr_watch('personality_mode')
#        
#    @property
#    def name(self):
#        return self._name
#    @name.setter
#    def name(self, value):
#        self._name = value
#        sig = 'attr_watch_name'
#        if sig in self._emitters:
#            self.emit(sig, object=self, name='name', value=value)
#        self.emit('property_changed', obj=self, property='name')
#    @property
#    def manufacturer(self):
#        return self._manufacturer
#    @manufacturer.setter
#    def manufacturer(self, value):
#        self._manufacturer = value
#        sig = 'attr_watch_manufacturer'
#        if sig in self._emitters:
#            self.emit(sig, object=self, name='name', value=value)
#        self.emit('property_changed', obj=self, property='manufacturer')

class DefinedDevice(Device):
    '''A Device Definition
    
    :Events:
        'new_attribute' (device_id, attribute_id)
    '''
    
    _saved_class_name = 'DefinedDevice'
    #_saved_attributes = ['channel_count']
    _Properties = {'channel_count':dict(default=0, fvalidate='_ch_count_validate'), 
                   'pan_range':dict(type=int), 
                   'tilt_range':dict(type=int)}
    _SettingsProperties = ['channel_count', 'pan_range', 'tilt_range']
    def __init__(self, **kwargs):
        self.GlobalAttributes = kwargs.get('GlobalAttributes')
#        self._channel_count = 0
        super(DefinedDevice, self).__init__(**kwargs)
        self.register_signal('chan_update', 'group_added', 'definition_update')
#        self.register_attr_watch('manufacturer')
#        self.register_attr_watch('channel_count')
        if 'deserialize' not in kwargs:
            self.channel_count  = kwargs.get('channel_count', 0)
        #if not hasattr(self, 'Attributes'):
        #    self.Attributes = {}
        self._update_attributes()
        keys = ['child_added', 'child_removed', 'child_index_changed']
        self.Attributes.bind(**dict(zip(keys, [self.on_attribute_index_changed]*len(keys))))
        
    def add_attribute(self, **kwargs):
        '''
        :Parameters:
            'attribute' : instance of a global attribute to copy from.
                this will soon be changed
            'chan_index' : int, relative channel index for the new attribute
            'id' : id for the new attribute.  Default is None (which assigns a new uuid)
        '''
        #kwargs.setdefault('chan_index', self.find_max_channel() + 1)
        attribute   = kwargs.get('attribute')
        kwargs.update({'parent_device_id':self.id, 'osc_address':attribute.name, 
                       'parent_device':self})
        kwargs.update({'GlobalAttribute':attribute})
        #self.add_child_object(kwargs)
        kwargs.update({'root_category':self.root_category.parent.subcategories['attribute']})
        new_attribute = self.Attributes.add_child(Attribute.DefinitionAttribute, **kwargs)
        #new_attribute.connect('chan_index_changed', self.on_attribute_chan_index_changed)
        #self.Attributes.update({new_attribute.id:new_attribute})
        #self._update_attributes()
        self.emit('new_attribute', device_id=self.id, attribute_id=new_attribute.id)
        self.emit('child_added', device_id=self.id, attribute_id=new_attribute.id)
        return new_attribute
    
    def del_attribute(self, **kwargs):
        attrib_id = kwargs.get('id')
        attribute = self.Attributes.get(attrib_id)
        if attribute:
            #if attribute.chan_index == self.channel_count:
            #    self.channel_count -= 1
            #elif attribute.sixteen_bit and attribute.chan_index + 1 == self.channel_count:
            #    self.channel_count -= 2
            attribute.unbind(self.on_attribute_index_changed)
            self.Attributes.del_child(attribute)
            self._update_attributes()
            self.emit('del_attribute', device_id=self.id, attribute_id=attrib_id, device=self)
        
#    @property
#    def channel_count(self):
#        return self._channel_count
#    @channel_count.setter
#    def channel_count(self, value):
#        value = int(value)
#        if value >= self.find_max_channel():
#            self._channel_count = value
#            self.emit('property_changed', obj=self, property='channel_count')
        
    def _ch_count_validate(self, value):
        return value >= self.Attributes.find_max_index()
    
    def _update_attributes(self):
        #self.attr_by_chan.clear()
        #for key, val in self.Attributes.iteritems():
        #    self.attr_by_chan.update({val.chan_index:val})
        max_chan = self.Attributes.find_max_index()
        if max_chan and self.Attributes.indexed_items[max_chan].sixteen_bit:
            max_chan += 1
        if max_chan > self.channel_count:
            self.channel_count = max_chan
##        if not hasattr(self, 'Color'):
##            color_attribs = {}
##            for attrib in self.Attributes.itervalues():
##                if attrib.name.lower() in ['red', 'green', 'blue']:
##                    color_attribs.update({attrib.name.lower():attrib})
##            if len(color_attribs) == 3:
##                self.add_color_group(color_attribs)
#            
#    def add_color_group(self, attribs):
#        factory = DeviceSystem.DeviceFactory()
#        kwargs = {'Name':'Color', 'parent_device':self, 'osc_address':'Color'}
#        self.add_child_object(kwargs)
#        group = factory.make(type='ColorGroupRGB', base=self.base_class, kwargs=kwargs)
#        for key, val in attribs.iteritems():
#            group.Attributes.update({val.id:val})
#            group.set_color_map(attribute=val, color=key)
#        self.Color = group
#        self.emit('group_added', device=self, group=group)
#    def find_max_channel(self):
#        if len(self.attr_by_chan) == 0:
#            return 0
#        return max(self.attr_by_chan.keys())
        
    def on_attribute_index_changed(self, **kwargs):
        self._update_attributes()
        self.emit('chan_update', device=self, attribute=kwargs.get('obj'))
        
    def _deserialize_child(self, d):
        if d['saved_class_name'] == 'DefinitionAttribute':
            kwargs = {}
            if self.osc_enabled:
                kwargs.update({'osc_address':d['attrs'].get('osc_address')})
            #self.add_child_object(kwargs)
            kwargs.update({'root_category':self.root_category.parent.subcategories['attribute'], 
                           'parent_device':self})
            gl_attrib = self.GlobalAttributes.get(d['attrs']['GlobalAttribute_id'])
            kwargs.update({'GlobalAttribute':gl_attrib, 'deserialize':d})
            attribute = self.Attributes.add_child(Attribute.DefinitionAttribute, **kwargs)
            #attribute.bind(Index=self.on_attribute_index_changed)
            #self.emit('new_attribute', device_id=self.id, attribute_id=attribute.id)
            return attribute
        return super(Device, self)._deserialize_child(d)
        

class PatchedDevice(Device):
    '''A Device that has been patched
    
    :Parameters:
        '_DefinedDevice_' : instance of a device definition to use for the new device.
            this will change soon
        'definition_id' : id of the device definition used to make this device. (for later use)
        'device_num' : int, index of the device to be used in the UI (for organization)
        'universe_id' : id of the DMX universe this device belongs to
        'start_chan' : int, starting DMX channel
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the device.  default is None.
    '''
    _saved_class_name = 'PatchedDevice'
    _saved_attributes = ['device_num', 'universe_id', 'start_chan', 'definition_id', 'pan_invert', 'tilt_invert', 'power_control_attribute_id']
    _saved_child_objects = ['Groups']
    signals_to_register = ['device_num_update', 'check_device_num']
    #_Properties = {'device_num':dict(type=int, default=None, fvalidate='_device_num_validate')}
    _Properties = {'power_control_attribute':{}, 'Power':dict(default=False)}
    group_classes = [PositionGroup, ColorGroup, DimmerGroup]
    def __init__(self, **kwargs):
        self.DeviceSystem = kwargs.get('DeviceSystem')
        kwargs.setdefault('root_category', self.DeviceSystem.root_category.subcategories['device'])
        kwargs.setdefault('osc_address', str(kwargs.get('Index')))
        self.definition = kwargs.get('definition')
        self.attr_by_chan = {}
        self.ungrouped_attributes = {}
        #self.saved_child_objects = set(['Groups'])
#        self._device_num = None
        self._pan_invert = False
        self._tilt_invert = False
        super(PatchedDevice, self).__init__(**kwargs)
        self.bind(device_num=self._on_device_num_set, 
                  power_control_attribute=self.on_power_control_attribute_set, 
                  Power=self.on_Power_set)
        #self.register_attr_watch('pan_invert')
        #self.register_attr_watch('tilt_invert')
        self.add_osc_child_nodes()
        if 'deserialize' not in kwargs:
            self.definition_id = getattr(self.definition, 'id', None)
#            self._device_num = kwargs.get('device_num')
            #self.device_num = kwargs.get('device_num')
            self.start_chan = kwargs.get('start_chan')
            self.universe_id = kwargs.get('universe_id')
            self.power_control_attribute_id = kwargs.get('power_control_attribute_id')
            self.power_control_attribute = kwargs.get('power_control_attribute')
            self.Groups = {}
            gr_kwargs = dict(osc_parent_node=self.group_osc_node, 
                             root_category=self.root_category.parent.subcategories['attribute'])
            for grpcls in self.group_classes:
                key = grpcls._saved_class_name
                group = grpcls(**gr_kwargs)
                self.Groups.update({key:group})
            for key in ['pan_range', 'tilt_range']:
                value = getattr(self.definition, key)
                if value is not None:
                    setattr(self.Groups['Position'], key, value)
#        else:
#            self._device_num = kwargs.get('deserialize')['attrs']['device_num']
        if self.definition:
            self._init_attributes(self.definition.Attributes.copy())
            for key in ['name', 'description']:
                if getattr(self, key) == '':
                    setattr(self, key, getattr(self.definition, key))
            self.definition.connect('definition_update', self.on_definition_update)
        
#    @property
#    def device_num(self):
#        return self._device_num
#    @device_num.setter
#    def device_num(self, value):
#        value = int(value)
#        old = self._device_num
#        self.emit('check_device_num', device=self, value=value, old=old)
    @property
    def device_num(self):
        return self.Index
    @device_num.setter
    def device_num(self, value):
        self.Index = value

    def _on_device_num_set(self, **kwargs):
        value = kwargs.get('value')
        old = kwargs.get('old')
        self.emit('device_num_update', device=self, value=value, old=old)
        
    def _device_num_validate(self, value):
        return not(value in self.DeviceSystem.DevicesByNum)
        
#    def _set_device_num(self, value):
#        old = self._device_num
#        self._device_num = value
#        self.emit('device_num_update', device=self, value=value, old=old)
#        self.emit('property_changed', obj=self, property='device_num')
            
    @property
    def end_chan(self):
        start = self.start_chan
        if start is not None:
            return start + self.channel_count - 1
        return None
        
    @property
    def pan_invert(self):
        return self._pan_invert
    @pan_invert.setter
    def pan_invert(self, value):
        self._pan_invert = value
        if hasattr(self, 'Groups') and 'Position' in self.Groups:
            self.Groups['Position'].pan_invert = value
        sig = 'attr_watch_pan_invert'
        if sig in self._emitters:
            self.emit(sig, object=self, name='pan_invert', value=value)
    @property
    def tilt_invert(self):
        return self._tilt_invert
    @tilt_invert.setter
    def tilt_invert(self, value):
        self._tilt_invert = value
        if hasattr(self, 'Groups') and 'Position' in self.Groups:
            self.Groups['Position'].tilt_invert = value
        sig = 'attr_watch_tilt_invert'
        if sig in self._emitters:
            self.emit(sig, object=self, name='tilt_invert', value=value)
            
    def repatch(self, **kwargs):
        univ_id = kwargs.get('universe_id')
        start = kwargs.get('start_chan')
        self.universe_id = univ_id
        self.start_chan = start
        self.attr_by_chan.clear()
        for attrib in self.Attributes.itervalues():
            attrib.repatch(**kwargs)
            self.attr_by_chan.update({attrib.chan:attrib})            
        
    def on_power_control_attribute_set(self, **kwargs):
        attribute = kwargs.get('value')
        if attribute:
            self.power_control_attribute_id = attribute.id
            group = PowerControlGroup(device=self, attribute_name='Power', 
                                      osc_parent_node=self.group_osc_node)
            group.add_member(**{attribute.id:attribute})
            self.Groups.update({group.name:group})
        
    def on_Power_set(self, state):
        group = self.Groups.get('Power')
        if group:
            group.value = state            
        
    def add_osc_child_nodes(self):
        #if not hasattr(self, 'attribute_osc_node'):
        #    self.attribute_osc_node = self.osc_node.add_new_node(name='Attributes')
        if not hasattr(self, 'group_osc_node'):
            self.group_osc_node = self.osc_node.add_new_node(name='Groups')
            
    def _deserialize_child(self, d):
        if d['attrs']['base_class'] == 'Group':
            self.add_osc_child_nodes()
            cls_d = dict(zip([cls._saved_class_name for cls in self.group_classes], self.group_classes))
            obj = cls_d[d['saved_class_name']](deserialize=d, osc_parent_node=self.group_osc_node, 
                                               root_category=self.root_category.parent.subcategories['attribute'])
            if obj.saved_class_name == 'Position':
                obj.pan_invert = self.pan_invert
                obj.tilt_invert = self.tilt_invert
            return obj
        #return super(PatchedDevice, self)._deserialize_child(d)
    
    @property
    def channel_count(self):
        return self.definition.channel_count

    def _init_attributes(self, attributes):
        new_kwargs = {'universe_id':self.universe_id, 'parent_device_id':self.id}
        self.Attributes.clear()
        self.attr_by_chan.clear()
        for key, val in attributes.iteritems():
            chan = self.start_chan + val.chan_index - 1
            new_kwargs.update({'chan':chan, 'osc_address':val.name, 'DefinitionAttribute':val, 
                               'root_category':self.root_category.parent.subcategories['attribute'], 
                               'Index':val.Index, 'parent_device':self})
            new_attribute = self.Attributes.add_child(Attribute.PatchedAttribute, **new_kwargs)
            
            #self.Attributes.update({new_attribute.id:new_attribute})
            self.attr_by_chan.update({new_attribute.chan:new_attribute})
            if new_attribute.sixteen_bit:
                lsb = new_attribute.lsb_attribute
                self.attr_by_chan.update({lsb.chan:lsb})
            
            self.emit('new_attribute', device_id=self.id, attribute_id=new_attribute.id)
            self.emit('child_added', device_id=self.id, attribute_id=new_attribute.id)
            
        for g in self.Groups.itervalues():
            g.device = self
            g.search_for_members()
        if not hasattr(self, 'ParameterGroups'):
            self.ParameterGroups = {}
        for key, val in self.Attributes.iteritems():
            if len(val.users) == 0:
                self.ungrouped_attributes.update({key:val})
            group = ParameterGroup(device=self, attribute_name=val.name, osc_parent_node=self.group_osc_node)
            if group.valid:
                self.ParameterGroups.update({val.name:group})
                
    def on_definition_update(self, **kwargs):
        pass
